﻿using System;
namespace ScatterView
{
    public class MersenneTwister : Random
    {
        private static readonly uint[] _mag01;
        private readonly uint[] _mt;
        private short _mti;
        private const double FiftyThreeBitsOf1s = 9007199254740991;
        private const double Inverse53BitsOf1s = 1.1102230246251568E-16;
        private const double InverseOnePlus53BitsOf1s = 1.1102230246251565E-16;
        private const uint LowerMask = 0x7fffffff;
        private const int M = 0x18d;
        private const uint MatrixA = 0x9908b0df;
        private const int N = 0x270;
        private const double OnePlus53BitsOf1s = 9007199254740992;
        private const uint TemperingMaskB = 0x9d2c5680;
        private const uint TemperingMaskC = 0xefc60000;
        private const uint UpperMask = 0x80000000;

        // Methods
        static MersenneTwister()
        {
            uint[] numArray = new uint[2];
            numArray[1] = 0x9908b0df;
            _mag01 = numArray;
        }

        public MersenneTwister()
            : this(new Random().Next())
        {
        }

        public MersenneTwister(int[] initKey)
        {
            this._mt = new uint[0x270];
            if (initKey == null)
            {
                throw new ArgumentNullException("initKey");
            }
            uint[] key = new uint[initKey.Length];
            for (int i = 0; i < initKey.Length; i++)
            {
                key[i] = (uint)initKey[i];
            }
            this.init(key);
        }

        public MersenneTwister(int seed)
        {
            this._mt = new uint[0x270];
            this.init((uint)seed);
        }

        private double compute53BitRandom(double translate, double scale)
        {
            ulong num = this.GenerateUInt32() >> 5;
            ulong num2 = this.GenerateUInt32() >> 6;
            return ((((num * 67108864.0) + num2) + translate) * scale);
        }

        protected uint GenerateUInt32()
        {
            uint num;
            short num3;
            if (this._mti >= 0x270)
            {
                short index = 0;
                while (index < 0xe3)
                {
                    num = (this._mt[index] & 0x80000000) | (this._mt[index + 1] & 0x7fffffff);
                    this._mt[index] = (this._mt[index + 0x18d] ^ (num >> 1)) ^ _mag01[(int)((IntPtr)(num & 1))];
                    index = (short)(index + 1);
                }
                while (index < 0x26f)
                {
                    num = (this._mt[index] & 0x80000000) | (this._mt[index + 1] & 0x7fffffff);
                    this._mt[index] = (this._mt[index + -227] ^ (num >> 1)) ^ _mag01[(int)((IntPtr)(num & 1))];
                    index = (short)(index + 1);
                }
                num = (this._mt[0x26f] & 0x80000000) | (this._mt[0] & 0x7fffffff);
                this._mt[0x26f] = (this._mt[0x18c] ^ (num >> 1)) ^ _mag01[(int)((IntPtr)(num & 1))];
                this._mti = 0;
            }
            this._mti = (short)((num3 = this._mti) + 1);
            num = this._mt[num3];
            num ^= temperingShiftU(num);
            num ^= temperingShiftS(num) & 0x9d2c5680;
            num ^= temperingShiftT(num) & 0xefc60000;
            return (num ^ temperingShiftL(num));
        }

        private void init(uint[] key)
        {
            int num3;
            this.init((uint)0x12bd6aa);
            int length = key.Length;
            int index = 1;
            int num2 = 0;
            for (num3 = (0x270 > length) ? 0x270 : length; num3 > 0; num3--)
            {
                this._mt[index] = ((this._mt[index] ^ ((this._mt[index - 1] ^ (this._mt[index - 1] >> 30)) * 0x19660d)) + key[num2]) + ((uint)num2);
                this._mt[index] &= uint.MaxValue;
                index++;
                num2++;
                if (index >= 0x270)
                {
                    this._mt[0] = this._mt[0x26f];
                    index = 1;
                }
                if (num2 >= length)
                {
                    num2 = 0;
                }
            }
            for (num3 = 0x26f; num3 > 0; num3--)
            {
                this._mt[index] = (this._mt[index] ^ ((this._mt[index - 1] ^ (this._mt[index - 1] >> 30)) * 0x5d588b65)) - ((uint)index);
                this._mt[index] &= uint.MaxValue;
                index++;
                if (index >= 0x270)
                {
                    this._mt[0] = this._mt[0x26f];
                    index = 1;
                }
            }
            this._mt[0] = 0x80000000;
        }

        private void init(uint seed)
        {
            this._mt[0] = seed & uint.MaxValue;
            this._mti = 1;
            while (this._mti < 0x270)
            {
                this._mt[this._mti] = (0x6c078965 * (this._mt[this._mti - 1] ^ (this._mt[this._mti - 1] >> 30))) + ((uint)this._mti);
                this._mt[this._mti] &= uint.MaxValue;
                this._mti = (short)(this._mti + 1);
            }
        }

        public override int Next()
        {
            return this.Next(0x7fffffff);
        }

        public override int Next(int maxValue)
        {
            if (maxValue > 1)
            {
                return (int)(this.NextDouble() * maxValue);
            }
            if (maxValue < 0)
            {
                throw new ArgumentOutOfRangeException();
            }
            return 0;
        }

        public override int Next(int minValue, int maxValue)
        {
            if (maxValue <= minValue)
            {
                throw new ArgumentOutOfRangeException();
            }
            if (maxValue == minValue)
            {
                return minValue;
            }
            return (this.Next(maxValue - minValue) + minValue);
        }

        public override void NextBytes(byte[] buffer)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException();
            }
            int length = buffer.Length;
            for (int i = 0; i < length; i++)
            {
                buffer[i] = (byte)this.Next(0x100);
            }
        }

        public override double NextDouble()
        {
            return this.compute53BitRandom(0.0, 1.1102230246251565E-16);
        }

        public double NextDouble(bool includeOne)
        {
            if (!includeOne)
            {
                return this.NextDouble();
            }
            return this.compute53BitRandom(0.0, 1.1102230246251568E-16);
        }

        public double NextDoublePositive()
        {
            return this.compute53BitRandom(0.5, 1.1102230246251568E-16);
        }

        public float NextSingle()
        {
            return (float)this.NextDouble();
        }

        public float NextSingle(bool includeOne)
        {
            return (float)this.NextDouble(includeOne);
        }

        public float NextSinglePositive()
        {
            return (float)this.NextDoublePositive();
        }

        public virtual uint NextUInt32()
        {
            return this.GenerateUInt32();
        }

        public virtual uint NextUInt32(uint maxValue)
        {
            return (uint)(((double)this.GenerateUInt32()) / (4294967295 / ((double)maxValue)));
        }

        public virtual uint NextUInt32(uint minValue, uint maxValue)
        {
            if (minValue >= maxValue)
            {
                throw new ArgumentOutOfRangeException();
            }
            return (((uint)(((double)this.GenerateUInt32()) / (4294967295 / ((double)(maxValue - minValue))))) + minValue);
        }

        private static uint temperingShiftL(uint y)
        {
            return (y >> 0x12);
        }

        private static uint temperingShiftS(uint y)
        {
            return (y << 7);
        }

        private static uint temperingShiftT(uint y)
        {
            return (y << 15);
        }

        private static uint temperingShiftU(uint y)
        {
            return (y >> 11);
        }

    }
}